#include "SkiaGraphicState.h"
#include "_DoFontId.h"
#include "DoString.h"
#include "MatrixUtils.h"

void SkiaGraphicState::GraphicState::stroke(SkPaint &paint)
{
	switch(mLineCap) {
		case 0:
			paint.setStrokeCap(SkPaint::kButt_Cap);
			break;
		case 1:
			paint.setStrokeCap(SkPaint::kRound_Cap);
			break;
		case 2:
			paint.setStrokeCap(SkPaint::kSquare_Cap);
			break;
	}
	switch(mLineJoin) {
		case 0:
			paint.setStrokeJoin(SkPaint::kMiter_Join);
			break;
		case 1:
			paint.setStrokeJoin(SkPaint::kRound_Join);
			break;
		case 2:
			paint.setStrokeJoin(SkPaint::kBevel_Join);
			break;
	}
	paint.setColor(mColor);
	paint.setStrokeWidth(mLineWidth);
	paint.setStrokeMiter(mMiterLimit);
	paint.setStyle(SkPaint::kStroke_Style);	
}

void SkiaGraphicState::GraphicState::fill(SkPaint &paint)
{
	paint.setColor(mColor);
	paint.setStyle(SkPaint::kFill_Style);		
}

void SkiaGraphicState::GraphicState::text(SkPaint &paint, float scale)
{
	paint.setColor(mColor);
	paint.setStyle(SkPaint::kFill_Style);
	paint.setTextSize(mFontSize * scale);	
}

void SkiaGraphicState::RGBtoCMYK(float color[4]) 
{
	if (color[0]==0 && color[1]==0 && color[2]==0) {
		color[0] = 0;
		color[1] = 0;
		color[2] = 0;
		color[3] = 1.0;
	} else if (color[0]==1.0 && color[1]==1.0 && color[2]==1.0) {
		color[0] = 0;
		color[1] = 0;
		color[2] = 0;
		color[3] = 0;
	} else {
		color[0] = 1.0 - color[0];
		color[1] = 1.0 - color[1];
		color[2] = 1.0 - color[2];
		float minK = min(color[0], min(color[1], color[2]));
		color[0] = (color[0] - minK) / (1.0 - minK);
		color[1] = (color[1] - minK) / (1.0 - minK);
		color[2] = (color[2] - minK) / (1.0 - minK);
		color[3] = minK;
	}
}

void SkiaGraphicState::CMYKtoRGB(float color[4]) 
{
	color[0] = 1.0 - (color[0] * (1.0 - color[3]) + color[3]);
	color[1] = 1.0 - (color[1] * (1.0 - color[3]) + color[3]);
	color[2] = 1.0 - (color[2] * (1.0 - color[3]) + color[3]);
	color[3] = 1.0;
}
	
SkiaGraphicState::SkiaGraphicState(SkBitmap &bitmap, float scale)
: _DoObject()
, mScale(scale)
, mBitmap(bitmap)
, mCanvas(mBitmap)
, mPaint()
, mFont(0)
, mState()
, mStateStack()
{
	mObjType = eGraphicState;
			
	mPaint.setAntiAlias(true);
	mPaint.setColor(SkColorSetRGB(255, 255, 255));
	mPaint.setStyle(SkPaint::kFill_Style);
	SkRect rect = SkRect::MakeWH(mBitmap.width(), mBitmap.height());
	mCanvas.drawRect(rect, mPaint);
		
/*  [scale-x    skew-x      trans-x]   [X]   [X']
	[skew-y     scale-y     trans-y] * [Y] = [Y']
	[persp-0    persp-1     persp-2]   [1]   [1 ]
*/
	mState.mCtm.setTranslate(0, mBitmap.height());
	mState.mCtm.preScale(scale, -scale, 0, 0);
}

SkiaGraphicState::~SkiaGraphicState()
{
}

void SkiaGraphicState::gsave()
{
	mCanvas.save();
	mStateStack.push(mState);
}

void SkiaGraphicState::grestore() 
{
	mCanvas.restore();
	if (mStateStack.size()) {
		mState = mStateStack.top();
		mStateStack.pop();
	}
}

void SkiaGraphicState::rectclip(int left, int top, int right, int bottom)
{
	SkRect rect;
	rect.fLeft = left;
	rect.fTop = top;
	rect.fRight = left + right;
	rect.fBottom = top + bottom;
	
	SkPath path;
	path.addRect(rect);
	
	mCanvas.setMatrix(mState.mCtm);
	mCanvas.clipPath(path);
	mCanvas.resetMatrix();
}

void SkiaGraphicState::setgray(float gray)
{
	mState.mColor = SkColorSetRGB(255 * gray, 255 * gray, 255 * gray);
}

void SkiaGraphicState::selectfont(CRefPtr< _DoObject > font, float fontMatrix[6])
{
	if (mState.mFontDict.get() != font.get()) {
		mState.mFontDict = font;
		CRefPtr< _DoObject > name = toDict(font)->get(CRefPtr< _DoObject >(new DoName("FID")));		
		if (mFont != 0) {
			mFont->unref();
			mFont = 0;
		}
		mFont = _DoFontId::getFont(font);
	}
	mState.mFontSize = fontMatrix[0];
}

void SkiaGraphicState::moveto(float x, float y) 
{
	mState.mCurrPoint.set(x, y);
	mState.mCurrPath.moveTo(x, y);
}

void SkiaGraphicState::convertString(DoString* str, std::string& stdstr)
{
	switch(str->getType()) {
		case DoString::ePlain:
			{
				if (mState.mFontDict.get()) {
					CRefPtr< _DoObject > obj1 = toDict(mState.mFontDict)->get(CRefPtr< _DoObject >(new DoName("Encoding")));		
					CRefPtr< _DoObject > obj2 = toDict(mState.mFontDict)->get(CRefPtr< _DoObject >(new DoName("CharStrings")));	
					DoArray *cEncoding = toArray(obj1);
					DoDictionary *cCharStrings = toDict(obj2);
					
					stdstr = str->getPlain();	
					const unsigned char *blob = (const unsigned char *)stdstr.data();
					std::ostringstream oss;
					for (size_t i = 0; i < stdstr.size(); i++) {
						CRefPtr< _DoObject > charCode = cEncoding->get((size_t)blob[i]);
						CRefPtr< _DoObject > glyph = cCharStrings->get(charCode);
						int index = toNumber(glyph)->getInt();
						oss.put((char)index);
						oss.put(0);
					}
					stdstr = oss.str();
					mPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
				}
			}
			break;
		case DoString::eHex:
			{
				stdstr = str->getHex();
				const unsigned char *blob = (const unsigned char *)stdstr.data();
				std::ostringstream oss;
				for (size_t i = 0; i < stdstr.size(); i++) {
					oss.put(blob[i]);
					oss.put(0);
				}
				stdstr = oss.str();
				mPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
			}
			break;
		default:
			CXXTHROW;
	}	
}

void SkiaGraphicState::show(CRefPtr< _DoObject > str)
{
	mPaint.setTypeface(mFont);
	mState.text(mPaint, mScale);
	
	std::string text;
	convertString(toString(str), text);
	
	SkPoint pos;
	mState.mCtm.mapXY(mState.mCurrPoint.x(), mState.mCurrPoint.y(), &pos);
	mCanvas.drawText(text.data(), text.size(), pos.x(), pos.y(), mPaint);	
	
	mState.text(mPaint, 1.0);
	float xAdvance = mPaint.measureText(text.data(), text.size());
	mState.mCurrPoint.offset(xAdvance, 0.f);
	
	//bool success = SkImageEncoder::EncodeFile("skia-text.png", mBitmap, SkImageEncoder::kPNG_Type, 100);
}

SkPoint SkiaGraphicState::dtransform(float mat[6], SkPoint pt)
{
	SkMatrix matrx = MatrixUtils::toMatrix(mat);
	matrx.setTranslateX(0);
	matrx.setTranslateY(0);
	
	SkPoint outpt;
	matrx.mapXY(pt.x(), pt.y(), &outpt);
	return outpt;
}

void SkiaGraphicState::rmoveto(float x, float y)
{
	mState.mCurrPoint.set(mState.mCurrPoint.x() + x, mState.mCurrPoint.y() + y);
	mState.mCurrPath.rMoveTo(x, y);	
}

void SkiaGraphicState::rlineto(float x, float y)
{
	mState.mCurrPoint.set(mState.mCurrPoint.x() + x, mState.mCurrPoint.y() + y);
	mState.mCurrPath.rLineTo(x, y);
}

SkPoint SkiaGraphicState::currentpoint()
{
	return mState.mCurrPoint;
}

void SkiaGraphicState::concatmatrix(float m1[6], float m2[6], float out[6])
{
	SkMatrix mat1 = MatrixUtils::toMatrix(m1);
	SkMatrix mat2 = MatrixUtils::toMatrix(m2);
	SkMatrix mat3;
	
	mat3.setConcat(mat1, mat2);
	MatrixUtils::toArray(mat3, out);
}

void SkiaGraphicState::setlinewidth(float x)
{
	mState.mLineWidth = x;
	if (mState.mLineWidth < 1)
		mState.mLineWidth = 1;
}

void SkiaGraphicState::setlinecap(int x)
{
	mState.mLineCap = x;
}

void SkiaGraphicState::setlinejoin(int x) 
{
	mState.mLineJoin = x;
}

void SkiaGraphicState::setdash(float dash[], float dash_phase)
{
	#pragma message "not supported"
}

void SkiaGraphicState::setmiterlimit(float x) 
{
	mState.mMiterLimit = x;
}

void SkiaGraphicState::lineto(float x, float y)
{
	mState.mCurrPoint.set(x, y);
	mState.mCurrPath.lineTo(x, y);
}

void SkiaGraphicState::stroke()
{
	mState.stroke(mPaint);
	
	mCanvas.setMatrix(mState.mCtm);
	mCanvas.drawPath(mState.mCurrPath, mPaint);
	mCanvas.resetMatrix();
	
	mState.mCurrPath.reset();
}

void SkiaGraphicState::closepath()
{
	mState.mCurrPath.close();
}

void SkiaGraphicState::clip()
{
	mCanvas.setMatrix(mState.mCtm);
	mCanvas.clipPath(mState.mCurrPath);
	mCanvas.resetMatrix();
}

void SkiaGraphicState::newpath()
{
	mState.mCurrPath.reset();
}

void SkiaGraphicState::concat(const SkMatrix& mat)
{
	mState.mCtm.preConcat(mat);
}

void SkiaGraphicState::setcolorspace(const char *name)
{
	if (strcmp(name, "DeviceRGB") == 0) {
		mState.mColorSpace = GraphicState::eRGB;
	} else if (strcmp(name, "DeviceGray") == 0) {
		mState.mColorSpace = GraphicState::eGray;
	} else if (strcmp(name, "DeviceCMYK") == 0) {
		mState.mColorSpace = GraphicState::eCMYK;
	} else {
		CXXTHROW;
	}
}

void SkiaGraphicState::image(int w, int h, float mat[6], const std::ostringstream& oss)
{
	std::string pixels = oss.str();
		
	SkBitmap bitmap;
	bitmap.setConfig(SkBitmap::kARGB_8888_Config, w, h);
	bitmap.allocPixels();
	bitmap.setPixels((void *)pixels.data());
	//bool success = SkImageEncoder::EncodeFile("skia-image-0.png", bitmap, SkImageEncoder::kPNG_Type, 100);
	
	// flip the image
	SkMatrix mat1 = MatrixUtils::toMatrix(mat);
	mat1.postConcat(mState.mCtm);
	mCanvas.drawBitmapMatrix(bitmap, mat1);
	//bool success = SkImageEncoder::EncodeFile("skia-image-1.png", mBitmap, SkImageEncoder::kPNG_Type, 100);
}

void SkiaGraphicState::showpage()
{
	//SkImageEncoder::EncodeFile("skia-page.png", mBitmap, SkImageEncoder::kPNG_Type, 100);
}

void SkiaGraphicState::eoclip()
{
	#pragma message "not supported"	
}

void SkiaGraphicState::fill()
{
	mState.fill(mPaint);
	
	mCanvas.setMatrix(mState.mCtm);
	mCanvas.drawPath(mState.mCurrPath, mPaint);
	mCanvas.resetMatrix();
	
	mState.mCurrPath.reset();
}

void SkiaGraphicState::curveto(float x1, float y1, float x2, float y2, float x3, float y3)
{
	mState.mCurrPath.cubicTo(x1, y1, x2, y2, x3, y3);
	mState.mCurrPoint.set(x3, y3);	
}

void SkiaGraphicState::setrgbcolor(float red, float green, float blue)
{
	mState.mColor = SkColorSetRGB(255 * red, 255 * green, 255 * blue);
}

void SkiaGraphicState::scale(float sx, float sy)
{
	mState.mCtm.setScale(sx, sy);
}

SkPoint SkiaGraphicState::transform(float mat[6], SkPoint pt)
{
	SkMatrix matrx = MatrixUtils::toMatrix(mat);
	SkPoint outpt;
	matrx.mapXY(pt.x(), pt.y(), &outpt);
	return outpt;	
}

SkPoint SkiaGraphicState::stringwidth(CRefPtr< _DoObject > str)
{
	mPaint.setTypeface(mFont);
	mState.text(mPaint, mScale);

	std::string text;
	convertString(toString(str), text);
	
	float xAdvance = mPaint.measureText(text.data(), text.size());

	return SkPoint::Make(xAdvance, mState.mFontSize);
}

SkPoint SkiaGraphicState::idtransform(float mat[6], SkPoint pt)
{
	SkMatrix matrx = MatrixUtils::toMatrix(mat);
	SkMatrix inverse;
	
	matrx.invert(&inverse);
	inverse.setTranslateX(0);
	inverse.setTranslateY(0);
	
	SkPoint outpt;
	inverse.mapXY(pt.x(), pt.y(), &outpt);
	return outpt;	
}

void SkiaGraphicState::awidthshow(float cx, float cy, int achar, float ax, float ay, CRefPtr< _DoObject > str)
{
	mPaint.setTypeface(mFont);
	mState.text(mPaint, mScale);
	
	std::string text;
	convertString(toString(str), text);
	
	SkPoint pos;
	float xAdvance = 0.f;
	SkPaint::TextEncoding encoding = mPaint.getTextEncoding();
	switch(encoding) {
		case SkPaint::kUTF8_TextEncoding:
			for (size_t i = 0; i < text.size(); i++) {
				char aChar[2];
				aChar[0] = text[i]; aChar[1] = 0;
				
				mState.text(mPaint, mScale);
				
				mState.mCtm.mapXY(mState.mCurrPoint.x(), mState.mCurrPoint.y(), &pos);
				mCanvas.drawText(aChar, 1, pos.x(), pos.y(), mPaint);	
		
				mState.text(mPaint, 1.0);
				xAdvance = mPaint.measureText(aChar, 1);
				if (aChar[0] == achar) {
					mState.mCurrPoint.offset(xAdvance + ax + cx, 0.f + ay + cy);	
				} else {
					mState.mCurrPoint.offset(xAdvance + 0, 0.f + ay);
				}
			}
			break;
		case SkPaint::kGlyphID_TextEncoding:
			break;
		default:
			CXXTHROW;
	}
}

void SkiaGraphicState::setfont(CRefPtr< _DoObject > font)
{
	CRefPtr< _DoObject > obj = toDict(font)->get(newName("FontMatrix"));
	float mat[6];
	MatrixUtils::toMatrix(toArray(obj), mat);
	selectfont(font, mat);
}

void SkiaGraphicState::setcmykcolor(float c, float m, float y, float k)
{
	float data[4];
	data[0] = c; data[1] = m; data[2] = y; data[3] = k;
	CMYKtoRGB(data);
	
	mState.mColor = SkColorSetRGB(255 * data[0], 255 * data[1], 255 * data[2]);
}